#ifndef __OPERON_H__
#define __OPERON_H__
#include <string>
#include <exception>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <stdexcept>


#include "db_cxx.h"

#define WHERE std::cerr << __FILE__ << ":" << __LINE__<< std::endl

class Organism
	{
	private:
		std::string name;
		std::string letter3;
		int tax_id;
		int operon_id;
	public:
		static const Organism* HOMO_SAPIENS;
		static const Organism* MUS_MUSCULUS;
		
		Organism(const char *s,int tax,int id,const char* w3);
		const char* getName() const;
		const char* getTreeLetterCode() const;
		int getTaxId() const;
		int getOperonId() const;
	};

class Assembly
	{
	private:
		 const Organism* organism;
		 std::string source;
		 std::string version;
		 std::string longDesc;
		 int id;
	public:
		static const Assembly* HSA_NCBI_BUILD_36;
		Assembly(int _id,std::string _source,const Organism* _organism, std::string _version,std::string _longDesc);
		const Organism* getOrganism() const;
		const char* getVersion() const;
		const char* getSource() const;
		const char* getName() const;
	};


class Fid
	{
	public:
		
	};

/**
 * Operon
 */
class Operon
	{
	private:
		std::string db_home;
		DbEnv* env;
	public:
		Operon(const char* bbd_home);
		
		DbEnv* getEnvironment();
		const char* getHome() const;
		
		~Operon();	
		void open() throw(std::exception);
		void close() throw(std::exception);
	};


class ByteArray
	{
	private:
		char* array;
		int len;
	public:

		ByteArray(const void* p,int index,int length):array(NULL),len(length)
			{
			array= new char[len];
			std::memcpy(array,&((char*)p)[index],len*sizeof(char));
			}
		
		ByteArray(const ByteArray& cp)
			{
			len=cp.size();
			array= new char[len];			
			std::memcpy(array,cp.ptr(),len*sizeof(char));
			}


		 ~ByteArray()
			{
			delete[] array;
			}
		
	
		ByteArray& operator=(const ByteArray& cp)
			{
			if(this!=&cp)
				{
				delete[] array;
				len=cp.size();
				array= new char[len];
				std::memcpy(array,cp.ptr(),len*sizeof(char));
				}
			return *this;
			}

		bool operator==(const ByteArray& cp) const
			{
			if(this==&cp) return true;
			
			if(size() !=cp.size())
				{
				return false;
				}
				
			return std::memcmp( ptr(),cp.ptr(),size()*sizeof(char) )==0;
			}
		
		const void* ptr() const { return array;}
		int size() const { return len;}
	};

template<typename K,class V>
class Iterator
	{
	protected:
		bool _eofMet;
		bool _hasNextCalled;
		bool _hasNext;
		std::pair<K,V> _curr;
	public:
		Iterator():_eofMet(false), _hasNextCalled(false),_hasNext(false)
			{
			}
		virtual ~Iterator()
			{
			}
		virtual bool hasNext()=0;
		virtual std::pair<K,V> next()=0;
		virtual void close()=0;
	};

/**
 * Iterator for BerkeleyDB
 */
template<typename K,class V>
class CursorIterator:public Iterator<K,V>
	{
	protected:
		Dbt key;
		Dbt data;
		Dbc *cursor;
		virtual void fill()=0;
		virtual bool advance()=0;
	public:
		CursorIterator(Dbc *c):cursor(c)
			{
			}
		virtual ~CursorIterator()
			{
			close();
			}

		/** skips at best n elements and return the number of element skipped */
		virtual int64_t skip(int64_t n)
			{
			int64_t i=0;
			for(i=0;i< n && hasNext();++i)
				{
				next();
				}
			return i;
			}
	
		/** answers a vector of pairs key/value containing at best, n elements starting from index */
		virtual std::vector<std::pair<K,V> > asVector(int64_t index,int64_t n)
			{
			std::vector<std::pair<K,V> > v;
			if( skip(index) < index)
				{
				return v;
				}
			for(int64_t i=0;i< n && hasNext();++i)
				{
				v.push_back(next());
				}
			return v;
			}
	
		/** answers a vector of pairs key/value containing at best, n elements */
		virtual std::vector<std::pair<K,V> > asVector(int64_t n)
			{
			return asVector(0,n);
			}

		/** return wether there is another element available */
		virtual bool hasNext()
			{
			if(this->_eofMet) return false;
			if(this->_hasNextCalled) return this->_hasNext;
			this->_hasNextCalled=true;
			if(!advance())
				{
				close();
				return false;
				}
			this->_hasNext=true;
			fill();
			return true;
			}
		/** returns the next element */
		virtual std::pair<K,V> next()
			{
			if(!this->_hasNextCalled) hasNext();
			if(!this->_hasNext)throw std::runtime_error("next called but eof met");
			this->_hasNextCalled=false;
			this->_hasNext=false;
			return this->_curr;
			}

		/** close this iterator */
		virtual void close()
			{
			if(cursor!=NULL)
				{
				cursor->close();
				}
			cursor=NULL;
			this->_eofMet=true;
			this->_hasNextCalled=true;
 			this->_hasNext=false;
			}
	};

/**
 * Base class for all the Objects in Operon
 */
class AbstractEntity
	{
	protected:
		static void json(std::ostream& out,const char* s);
	public:
		AbstractEntity();
		
	};


/**
 * Base class for all the Databases in Operon
 */
template<typename K,class V>
class AbstractDatabase
 	{
 	private:
 		Operon* operon;
 		Db* database;
 	protected:
		/** constructor */
 		AbstractDatabase(Operon* op):operon(op),database(0) 
 			{
 			}
 		
 		std::string getAssemblySpecificPath(const Assembly* assembly) const
			{
			std::string s( "dbs/dna/");
			s.append(assembly->getOrganism()->getTreeLetterCode());
			s.append("/");
			s.append(assembly->getVersion());
			s.append("/");
			s.append(assembly->getSource());
			return s;
			}
			
		/** return the relative of this database <b>if</b> it is associated to a given assembly and to a given suffix (e.g CBuildDB )*/
		std::string  getAssemblySpecificName(const Assembly* assembly,const char* suffix) const
			{
			std::string s(getAssemblySpecificPath(assembly));
			s.append("/");
			s.append(assembly->getOrganism()->getTreeLetterCode());
			s.append("_");
			s.append(assembly->getSource());
			s.append("_");
			s.append(suffix);
			s.append("_");
			s.append(assembly->getVersion());
			s.append(".bdb");
			return s;
			}

		std::string getPath()
			{
			std::string path=getOperon()->getHome();
		 	path.append("/").append(getName());
			return path;
			}
 		
		class KeyValueIterator: public CursorIterator<K,V>
			{
			protected:
				AbstractDatabase<K,V>* owner;
				virtual void fill()
					{
					this->_curr = std::make_pair(
						owner->entryToKey(&(this->key),&(this->data)),
						owner->entryToValue(&(this->key),&(this->data))
						);
					}
				virtual bool advance()
					{
					return this->cursor->get(&(this->key),&(this->data),DB_NEXT)==0;
					}
			public:
				KeyValueIterator(Dbc *c,AbstractDatabase<K,V>* o):CursorIterator<K,V>::CursorIterator(c),owner(o)
					{
					}
				virtual ~KeyValueIterator()
					{
					close();
					}
				
			};

		class BetweenIterator: public KeyValueIterator
			{
			private:
				bool first;
				K begin;
				K end;
				bool includeLast;
				ByteArray* array;
			protected:
				
				virtual bool advance()
					{
					if(first)
						{
						first=false;
						array= this->owner->keyToByteArray(begin);
						
						this->key.set_data((void*)array->ptr());
						this->key.set_size(array->size());
						

						this->data.set_data((void*)array->ptr());
						this->data.set_size(0);

						

						if( this->cursor->get(&(this->key),&(this->data),DB_SET_RANGE)!=0) { close();return false;}
						}
					else
						{
						if( this->cursor->get(&(this->key),&(this->data),DB_NEXT)!=0) { close();return false;}
						}
					K k= this->owner->entryToKey(&(this->key),&(this->data));
					
					if(k< end) { return true;}
					if(k==end) { return includeLast;}
					
					close();
					return false;
					}
			public:
				BetweenIterator(Dbc *c,AbstractDatabase<K,V>* o,K begin,K end,bool includeLast):KeyValueIterator::KeyValueIterator(c,o),
					first(true),
					begin(begin),
					end(end),
					includeLast(includeLast),
					array(NULL)
					{

					
					}

				virtual ~BetweenIterator()
					{
					close();
					if(array!=NULL) delete array;
					}
				
			};
		

 	public:	
		typedef   K key_type;
		typedef   V value_type;
		typedef   Iterator<K,V> iterator_type;
		typedef   std::pair<K,V> pair_type;


 		Db* getDatabase()
 			{
 			return database;
 			}
 	
 		virtual ~AbstractDatabase()
 			{
 			close();
 			}
 		
 		Operon* getOperon()
 			{
 			return operon;
 			}
 		
 		virtual std::string getName() const=0;
 		
 		bool exist()
			{
			std::FILE* in=std::fopen(getPath().c_str(),"rb");
			if(in==NULL) return false;
			std::fclose(in);
			return true;
			}
 		
 		 void open() throw (std::exception)
 			{
 			if(database==0)
 				{
				if(!exist()) throw std::runtime_error("Cannot open database");
				try
					{
	 				database=new Db(getOperon()->getEnvironment(),0);
		 			
	 				
	 				database->open(NULL,
	 						getPath().c_str(),
	 						NULL,
	 						DB_UNKNOWN,
	 						DB_RDONLY,
	 						0
	 						);
					}
				catch(std::exception& err)
					{
					close();
					std::cerr << "Cannot open "<< getPath() << std::endl;
					throw err;
					}
 				}

 			}
 			
 			
 		 void close() throw (std::exception)
 			{
 			if(database!=0)
 				{
 				try {database->close(0); } catch(...) { }
 				delete database;
 				database=0;
 				}
 			}
 		
		iterator_type* listKeyValues()
			{
			Dbc *dbc=NULL;
		 	if(getDatabase()->cursor (0, &dbc, 0)!=0) throw std::runtime_error("Cannot create cursor");
			return new KeyValueIterator(dbc,this);
			}
	
		iterator_type* listBetween(K first,K last,bool includeLast)
			{
			Dbc *dbc=NULL;
		 	if(getDatabase()->cursor (0, &dbc, 0)!=0) throw std::runtime_error("Cannot create cursor");
			return new BetweenIterator(dbc,this,first,last,includeLast);
			}
		
		virtual ByteArray* keyToByteArray(const K) const=0;
 		virtual K entryToKey(const Dbt* key,const Dbt* value) const=0;
 		virtual V entryToValue(const Dbt* key,const Dbt* value) const=0;
 	};
 
 
 

 template<typename K,class V>
 class SingleKeyDatabase:public AbstractDatabase<K,V>
 	{
 	protected:
 		SingleKeyDatabase(Operon* op):AbstractDatabase<K,V>::AbstractDatabase(op)
 			{
 			}
 
 	public:
 		
 		
 		
 		virtual ~SingleKeyDatabase()
 			{
 			}
 	

		
 	 	virtual V* get(const K key) throw (std::exception) =0;

 		V* operator[](const K key) throw (std::exception)
 			{ 
   			return get(key); 
  			}
 	};
 
 
 
 
 template<typename K,class V>
 class DuplicateKeyDatabase:public AbstractDatabase<K,V>
 	{
 	protected:
 		DuplicateKeyDatabase(Operon* op):AbstractDatabase<K,V>::AbstractDatabase(op)
 			{
 			}
 		
		virtual ~DuplicateKeyDatabase()
 			{
 			}
 		
 		virtual std::vector<V> get(const K key) throw (std::exception) =0;

 		std::vector<V>  operator[](const K key) throw (std::exception)
 			{ 
   			return get(key); 
  			}
 			
 	};


#include "entities.h"

#endif


